#include	"../include/IOListener.h"
#include	"../include/Tickable.h"

#if defined(_WIN32)
#	define	FD_SETSIZE	4096
#	include	<WinSock2.h>
#	include	<map>
#	pragma	comment(lib, "ws2_32.lib")
#else
#	include	<cstdlib>
#	include	<stdexcept>
#	include <sys/epoll.h>
#	include <unistd.h>
#endif

#if defined(_WIN32)
class IOModule : public ITickable {
public:
	IOModule() : ITickable(), _r_set{ 0 }, _w_set{ 0 }, _listeners() {}
	~IOModule() {}

	void Add(int fd, IOListener * listener, int events) {
		SOCKET socket = (SOCKET)fd;
		
		if (socket == INVALID_SOCKET) return;
		if ((events & IO_READ) && !FD_ISSET(socket, &_r_set)) FD_SET(socket, &_r_set);
		if ((events & IO_WRITE) && !FD_ISSET(socket, &_w_set)) FD_SET(socket, &_w_set);

		_listeners[fd] = listener;
	}

	void Wait(int fd, IOListener * listener, int events) {
		SOCKET socket = (SOCKET)fd;
		if (socket == INVALID_SOCKET) return;

		fd_set read_set, write_set;
		FD_ZERO(&read_set);
		FD_ZERO(&write_set);
		if (events & IO_READ) FD_SET(socket, &read_set);
		if (events & IO_WRITE) FD_SET(socket, &write_set);
		select(0, &read_set, &write_set, 0, NULL);

		if (FD_ISSET(socket, &read_set)) listener->OnReadable();
		if (FD_ISSET(socket, &write_set)) listener->OnWritable();
	}

	void Del(int fd) {
		SOCKET socket = (SOCKET)fd;
		if (FD_ISSET(socket, &_r_set)) FD_CLR(socket, &_r_set);
		if (FD_ISSET(socket, &_w_set)) FD_CLR(socket, &_w_set);
		auto it = _listeners.find(fd);
		if (it != _listeners.end()) _listeners.erase(it);
	}

	virtual void OnBreath() override {
		static fd_set cr_set = { 0 };
		static fd_set cw_set = { 0 };
		static struct timeval wait = { 0, 0 };

		int r_count = _r_set.fd_count;
		cr_set.fd_count = r_count;
		if (r_count > 0) memcpy(cr_set.fd_array, _r_set.fd_array, sizeof(SOCKET) * r_count);

		int w_count = _w_set.fd_count;
		cw_set.fd_count = w_count;
		if (w_count > 0) memcpy(cw_set.fd_array, _w_set.fd_array, sizeof(SOCKET) * w_count);

		if (select(0, &cr_set, &cw_set, NULL, &wait) <= 0) return;

		for (u_int n = 0; n < cr_set.fd_count; ++n) {
			auto it = _listeners.find((int)cr_set.fd_array[n]);
			if (it != _listeners.end()) it->second->OnReadable();
		}

		for (u_int n = 0; n < cw_set.fd_count; ++n) {
			auto it = _listeners.find((int)cw_set.fd_array[n]);
			if (it != _listeners.end()) it->second->OnWritable();
		}
	}

private:
	fd_set _r_set;
	fd_set _w_set;
	std::map<int, IOListener *> _listeners;
};
#else
class IOModule : public ITickable {
public:
	IOModule() : ITickable(), _epoll(epoll_create(1)) {}
	~IOModule() { if (_epoll >= 0) close(_epoll); }

	void Add(int fd, IOListener * listener, int events) {
		epoll_event ev;
		ev.data.ptr = listener;

		if (events & IO_READ) ev.events |= EPOLLIN;
		if (events & IO_WRITE) ev.events |= EPOLLOUT;

		epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, &ev);
	}

	void Wait(int fd, IOListener * listener, int events) {
		int wait_fd = epoll_create(1);
		if (wait_fd < 0) return;

		epoll_event ev;
		ev.events = EPOLLET | EPOLLRDHUP;

		if (events & IO_READ) ev.events |= EPOLLIN;
		if (events & IO_WRITE) ev.events |= EPOLLOUT;

		epoll_ctl(wait_fd, EPOLL_CTL_ADD, fd, &ev);

		if (epoll_wait(wait_fd, &ev, 1, -1) > 0) {
			if (ev.events & EPOLLIN) listener->OnReadable();
			if (ev.events & EPOLLOUT) listener->OnWritable();
		}

		close(wait_fd);
	}

	void Del(int fd) {
		epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL);
	}

	virtual void OnBreath() override {
		static epoll_event events[4096] = { 0 };

		int count = epoll_wait(_epoll, events, 4096, 0);
		if (count <= 0) return;

		for (int i = 0; i < count; ++i) {
			IOListener * listener = (IOListener *)events[i].data.ptr;
			if (events[i].events & EPOLLIN) listener->OnReadable();
			if (events[i].events & EPOLLOUT) listener->OnWritable();
		}
	}

private:
	int _epoll;
};
#endif

static IOModule * GetIOModule() {
	static IOModule * g_io = new IOModule();
	return g_io;
}

void IOListener::BindIO(int fd, int events) {
	GetIOModule()->Add(fd, this, events);
	_binded_io_fd = fd;
}

void IOListener::WaitIO(int event) {
	GetIOModule()->Wait(_binded_io_fd, this, event);
}

void IOListener::UnbindIO() {
	GetIOModule()->Del(_binded_io_fd);
}
